using System.Text.RegularExpressions;
using Cocona.Application;
using Cocona.Command;
using Cocona.ShellCompletion.Candidate;

namespace Cocona.ShellCompletion.Generators;

/// <summary>
/// Generates the shell completion code for Bash.
/// </summary>
public class BashCoconaShellCompletionCodeGenerator : ICoconaShellCompletionCodeGenerator
{
    private readonly string _appName;
    private readonly string _appCommandName;
    private readonly ICoconaCompletionCandidates _completionCandidates;

    public IReadOnlyList<string> Targets { get; } = new[] {"bash"};

    public BashCoconaShellCompletionCodeGenerator(
        ICoconaApplicationMetadataProvider applicationMetadataProvider,
        ICoconaCompletionCandidates completionCandidates
    )
    {
        _appName = Regex.Replace(applicationMetadataProvider.GetProductName(), "[^a-zA-Z0-9_]", "__");
        _appCommandName = applicationMetadataProvider.GetExecutableName();
        _completionCandidates = completionCandidates;
    }

    public void Generate(TextWriter writer, CommandCollection commandCollection)
    {
        writer.WriteLine($"#!/bin/bash");
        writer.WriteLine($"# Generated by Cocona {nameof(BashCoconaShellCompletionCodeGenerator)}");

        // root
        WriteRootCommandDefinition(writer, commandCollection);

        // Write common.sh
        using (var reader = new StreamReader(typeof(BashCoconaShellCompletionCodeGenerator).Assembly.GetManifestResourceStream("Cocona.ShellCompletion.Generators.Resources.bash_common.sh")!))
        {
            writer.Write(reader.ReadToEnd().Replace("APPCOMMANDNAMEPLACEHOLDER", _appCommandName).Replace("APPNAMEPLACEHOLDER", _appName));
        }
    }

    public void GenerateOnTheFlyCandidates(TextWriter writer, IReadOnlyList<CompletionCandidateValue> values)
    {
        writer.Write(string.Join(" ", values.Select(x => x.Value)));
    }

    private void WriteRootCommandDefinition(TextWriter writer, CommandCollection commandCollection)
    {
        var subCommands = commandCollection.All.Where(x => !x.IsHidden && !x.IsPrimaryCommand).ToArray();

        writer.WriteLine($"__cocona_{_appName}_commands_root() {{");
        foreach (var command in subCommands)
        {
            writer.WriteLine($"    __cocona_{_appName}_completion_define_command \"{command.Name}\"");
        }
        if (commandCollection.Primary != null)
        {
            WriteCommandDefineOptionAndArguments(writer, commandCollection.Primary);
        }
        writer.WriteLine($"    __cocona_{_appName}_completion_handle");
        writer.WriteLine("}");
        writer.WriteLine();

        // sub-commands
        foreach (var subCommand in subCommands)
        {
            WriteCommandDefinition(writer, $"root_{subCommand.Name}", subCommand);
        }
    }

    private void WriteCommandDefinition(TextWriter writer, string commandName, CommandDescriptor command)
    {
        var subCommands = command.SubCommands?.All.Where(x => !x.IsHidden && !x.IsPrimaryCommand).ToArray() ?? Array.Empty<CommandDescriptor>();

        writer.WriteLine($"__cocona_{_appName}_commands_{commandName}() {{");
        foreach (var subCommand in subCommands)
        {
            writer.WriteLine($"    __cocona_{_appName}_completion_define_command \"{subCommand.Name}\"");
        }
            
        WriteCommandDefineOptionAndArguments(writer, command);

        writer.WriteLine($"    __cocona_{_appName}_completion_handle");
        writer.WriteLine("}");
        writer.WriteLine();

        foreach (var subCommand in subCommands)
        {
            WriteCommandDefinition(writer, $"{commandName}_{subCommand.Name}", subCommand);
        }
    }

    private void WriteCommandDefineOptionAndArguments(TextWriter writer, CommandDescriptor command)
    {
        foreach (var option in command.Options.OfType<ICommandOptionDescriptor>().Concat(command.OptionLikeCommands).Where(x => !x.Flags.HasFlag(CommandOptionFlags.Hidden)))
        {
            writer.WriteLine($"    __cocona_{_appName}_completion_define_option \"--{option.Name}\" \"{FromOptionToCandidatesType(option)}\"");
        }
        foreach (var arg in command.Arguments)
        {
            writer.WriteLine($"    __cocona_{_appName}_completion_define_argument \"{arg.Name}\" \"{FromArgumentToCandidatesType(arg)}\"");
        }

        string FromOptionToCandidatesType(ICommandOptionDescriptor option)
        {
            if (option is CommandOptionDescriptor commandOption)
            {
                if (commandOption.OptionType == typeof(bool))
                {
                    return "bool";
                }
                else
                {
                    var candidates = _completionCandidates.GetStaticCandidatesFromOption(commandOption);
                    if (candidates.IsOnTheFly)
                    {
                        return $"onthefly:{option.Name}";
                    }
                    else
                    {
                        return candidates.Result!.ResultType switch
                        {
                            CompletionCandidateResultType.Default
                                => "default",
                            CompletionCandidateResultType.File
                                => "file",
                            CompletionCandidateResultType.Directory
                                => "directory",
                            CompletionCandidateResultType.Keywords
                                => $"keywords:{string.Join(":", candidates.Result!.Values.Select(x => x.Value))}",
                            _
                                => "default",
                        };
                    }
                }
            }
            else
            {
                return "default";
            }
        }

        string FromArgumentToCandidatesType(CommandArgumentDescriptor argument)
        {
            var candidates = _completionCandidates.GetStaticCandidatesFromArgument(argument);
            if (candidates.IsOnTheFly)
            {
                return $"onthefly:{argument.Name}";
            }
            else
            {
                return candidates.Result!.ResultType switch
                {
                    CompletionCandidateResultType.Default
                        => "default",
                    CompletionCandidateResultType.File
                        => "file",
                    CompletionCandidateResultType.Directory
                        => "directory",
                    CompletionCandidateResultType.Keywords
                        => $"keywords:{string.Join(":", candidates.Result!.Values.Select(x => x.Value))}",
                    _
                        => "default",
                };
            }
        }
    }
}